package com.sunday.gateway.mock.security.webflux.login;

import com.sunday.gateway.mock.security.webflux.login.handler.*;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerHttpBasicAuthenticationConverter;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.WebSessionServerLogoutHandler;
import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;

@Configuration
/**
 * 作用：启用@EnableWebFluxSecurity是为了在Spring WebFlux环境中启用Spring Security的支持。该注解负责激活Web安全配置的反应式版本，允许您自定义如何对HTTP请求进行保护。基于此，您可以定义各种安全规则，比如哪些路径是公开的，哪些需要身份验证，以及具有特定权限的用户可以访问哪些资源等。
 * 为什么必要：没有启用@EnableWebFluxSecurity时，Spring Security的反应式支持不会自动配置，这意味着您无法利用Spring Security提供的反应式安全特性来保护您的WebFlux应用。因为Spring WebFlux是基于非阻塞的、事件驱动的模型设计的，所以需要使用专门为这种模型设计的安全配置。
 */
@EnableWebFluxSecurity
/**
 * 作用：@EnableReactiveMethodSecurity允许您在Spring WebFlux项目中使用基于注解的方法级别安全性，如@PreAuthorize、@PostAuthorize、@Secured等。这意味着您可以在方法执行之前或之后应用安全策略，以及指定方法调用的权限要求。
 * 为什么必要：默认情况下，即使您的应用启用了Spring Security，方法级别的安全注解也不会自动生效。如果您想在Spring WebFlux应用中使用这些基于注解的安全控制，必须通过@EnableReactiveMethodSecurity来显式激活方法安全配置。它确保了Spring Security可以识别并处理这些注解，从而为您的方法提供声明式的权限验证。
 */
//@EnableMethodSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {


    /**
     * 内存用户配置
     * https://docs.spring.io/spring-security/reference/reactive/authorization/method.html
     * @see ReactiveSecurityAutoConfiguration.SpringBootWebFluxSecurityConfiguration.EnableWebFluxSecurityConfiguration
     * @see AuthenticationWebFilter#authenticate(ServerWebExchange, WebFilterChain, Authentication)
     * @see ServerHttpBasicAuthenticationConverter#apply(ServerWebExchange)
     */
    @Bean
    public MapReactiveUserDetailsService userDetailsService() {
        User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
        UserDetails rob = userBuilder.username("rob")
                .password("rob")
                .roles("USER")
//                .authorities(List.of(() -> "ROLE_USER", () -> "USER_LIST"))
                .build();
        UserDetails admin = userBuilder.username("admin")
                .password("admin")
                .roles("USER", "ADMIN")
//                .authorities(List.of(() -> "ROLE_USER", () -> "ROLE_ADMIN", () -> "USER_LIST", () -> "USER_GET"))
                .build();
        return new MapReactiveUserDetailsService(rob, admin);
    }

    /**
     * 0 = {ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter@8171}
     * 1 = {HttpHeaderWriterWebFilter@8172}
     * 2 = {CsrfWebFilter@8173}
     * 3 = {ReactorContextWebFilter@8174}
     * 4 = {SecurityContextServerWebExchangeWebFilter@8175}
     * 5 = {ServerRequestCacheWebFilter@8176}
     * 6 = {LogoutWebFilter@8177}
     * 7 = {ExceptionTranslationWebFilter@6524}
     * 8 = {AuthorizationWebFilter@8178}
     *
     * 在Web应用中，如HTTP基本认证的弹窗是由浏览器触发的，当服务器向浏览器发送一个带有状态码401 Unauthorized的响应，并且在响应头中包含WWW-Authenticate字段时，浏览器会自动展示一个认证的弹窗，提示用户输入用户名和密码。
     * 这个机制是一个由HTTP协议定义的标准认证方式，被称为HTTP Basic Authentication。弹窗界面由浏览器创建，并不是由服务端直接控制的，服务端只是通过HTTP响应头告知浏览器需要进行认证。
     * 在使用Spring Security的情况下，如果你对一个受保护的资源发起请求（而没有提供必要的认证信息，或者认证信息无效），Spring Security将返回一个401状态码，并且包含WWW-Authenticate的响应头，这会触发浏览器创建认证弹窗。
     *
     * @see ServerHttpSecurity#build()
     * @see org.springframework.boot.autoconfigure.security.SecurityProperties.User
     * @param http
     * @return
     */
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {

        DelegatingServerLogoutHandler logoutHandler = new DelegatingServerLogoutHandler(
                new WebSessionServerLogoutHandler(), new SecurityContextServerLogoutHandler()
        );

        http
                /**
                 * @see ExceptionTranslationWebFilter#filter(ServerWebExchange, WebFilterChain)
                 * @see HttpStatusServerAccessDeniedHandler#handle(ServerWebExchange, AccessDeniedException)
                 */
                .exceptionHandling(exceptionHandling -> exceptionHandling
//                        .authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/login"))
                                .accessDeniedHandler(new JsonServerAccessDeniedHandler())
                )
                .authorizeExchange(exchanges -> exchanges
                        .pathMatchers("/login/**", "/logout/**").permitAll()
                        .pathMatchers("/list").hasAnyRole("USER")
                        .pathMatchers("/get").hasAnyRole("ADMIN")
                        .pathMatchers("/user_list").hasAuthority("USER_LIST")
                        .pathMatchers("/user_get").hasAuthority("USER_GET")
                        .anyExchange().authenticated()
                )
//                .httpBasic(withDefaults())
                .formLogin(formLogin -> formLogin
                                .loginPage("/login")
//                        .authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/login?failure"))
                                // 无访问权限handler
                                .authenticationEntryPoint(new RedirectServerAuthenticationEntryPointHandler("/login"))
                                // 登陆失败handler
                                .authenticationFailureHandler(new JsonServerAuthenticationFailureHandler())
                                // 登录成功handler
                                .authenticationSuccessHandler(new JsonServerAuthenticationSuccessHandler())
                )
                .logout(logout -> logout
//                                .logoutHandler(logoutHandler)
                                .logoutSuccessHandler(new JsonServerLogoutSuccessHandler())
                                /**
                                 * @see LogoutWebFilter
                                 */
                                .requiresLogout(
                                        ServerWebExchangeMatchers
                                                .matchers(
                                                        ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/logout"),
                                                        ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout")
                                                )
                                )
                )
                .csrf(csrf -> csrf.disable())
        ;

        return http.build();
    }

}